SPB Git forge

spb/market-atlas

Public
12commits 1branches 0releases
1.1 MBsize
maindefault branch
10 days agolast push
TypeScript 96.7% SQL 1.6% CSS 0.8% JavaScript 0.5%
4.1 KB · 85 lines tsx
Raw Blame History
1import type { Metadata } from "next";2import Link from "next/link";3import { Kv, Page, PageHeader } from "@/components/ui/section";4import { StatusBadge } from "@/components/ui/status-badge";5import { api } from "@/lib/api";6import { EVENT_TYPE_LABEL, formatDateTime, instrumentHref } from "@/lib/format";7import type { MarketEvent } from "@/lib/types";89export const dynamic = "force-dynamic";1011export async function generateMetadata({ params }: { params: Promise<{ id: string }> }): Promise<Metadata> {12  const { id } = await params;13  try {14    const e = await api<MarketEvent>(`/v1/events/${encodeURIComponent(id)}`);15    return { title: e.title, description: e.summary ?? `${EVENT_TYPE_LABEL[e.type] ?? e.type} event recorded by Market Atlas.`, alternates: { canonical: `/events/${e.id}` }, robots: { index: false } };16  } catch {17    return { title: "Event" };18  }19}2021export default async function EventPage({ params }: { params: Promise<{ id: string }> }) {22  const { id } = await params;23  const e = await api<MarketEvent>(`/v1/events/${encodeURIComponent(id)}`);24  const url = typeof e.data.url === "string" ? (e.data.url as string) : null;25  return (26    <Page>27      <PageHeader28        kicker={29          <Link href={`/events?type=${e.type}`} className="hover:text-ink">30            Events · {EVENT_TYPE_LABEL[e.type] ?? e.type}31          </Link>32        }33        title={e.title}34        lead={e.summary ?? undefined}35        actions={<StatusBadge status={e.severity} />}36      />37      <div className="grid grid-cols-1 [&>*]:min-w-0 gap-6 lg:grid-cols-[1fr_320px]">38        <div className="rounded-md border border-rule bg-surface p-4">39          <h2 className="text-[11px] font-medium uppercase tracking-wide text-ink-3">Event data</h2>40          <Kv41            cols={1}42            className="mt-2"43            items={Object.entries(e.data)44              .filter(([, v]) => v != null && typeof v !== "object")45              .map(([k, v]) => [k.replace(/_/g, " "), String(v)] as [string, string])}46          />47          {Object.values(e.data).some((v) => v && typeof v === "object") && <pre className="mono mt-3 overflow-x-auto rounded bg-surface-2 p-3 text-xs">{JSON.stringify(Object.fromEntries(Object.entries(e.data).filter(([, v]) => v && typeof v === "object")), null, 2)}</pre>}48          {url && (49            <a href={url} target="_blank" rel="noopener noreferrer" className="mt-3 inline-block text-sm text-accent hover:underline">50              Open source document ↗51            </a>52          )}53        </div>54        <aside className="space-y-4">55          <div className="rounded-md border border-rule bg-surface p-4">56            <h2 className="text-[11px] font-medium uppercase tracking-wide text-ink-3">Provenance</h2>57            <Kv cols={1} className="mt-2" items={[["Time", formatDateTime(e.timestamp, { seconds: true })], ["Confidence", `${Math.round(e.confidence * 100)}%`], ["Independent sources", String(e.source_count)], ["Confirmed", e.confirmed_at ? formatDateTime(e.confirmed_at, { seconds: true }) : "single observation"], ["Event id", e.id]]} />58            <div className="mt-2 flex flex-wrap gap-1.5">59              {e.sources.map((s) => (60                <Link key={s} href={`/sources#${s}`} className="mono rounded border border-rule px-1.5 py-0.5 text-xs text-ink-2 hover:border-rule-strong">61                  {s}62                </Link>63              ))}64            </div>65          </div>66          {e.instruments.length > 0 && (67            <div className="rounded-md border border-rule bg-surface p-4">68              <h2 className="text-[11px] font-medium uppercase tracking-wide text-ink-3">Instruments</h2>69              <ul className="mt-2 divide-y divide-rule text-sm">70                {e.instruments.map((i) => (71                  <li key={i.id} className="py-1.5">72                    <Link href={instrumentHref(i.id)} className="hover:underline">73                      <span className="mono font-medium">{i.symbol ?? i.id}</span> {i.name && <span className="text-xs text-ink-3">{i.name}</span>}74                    </Link>75                  </li>76                ))}77              </ul>78            </div>79          )}80        </aside>81      </div>82    </Page>83  );84}85